﻿using System;
using System.Collections.Generic;
using System.Linq;
using FitNesseRunner.Model;

namespace FitNesseRunner
{
	public class ExecutionListProvider
	{
		public const string SetUpFolderName = "SetUp";
		public const string RootFolderName = "FitNesseRoot";
		public const string SuiteTearDownFolderName = "SuiteTearDown";
		public const string SuiteSetUpFolderName = "SuiteSetUp";
		public const string TearDownFolderName = "TearDown";
	}

	public class ExecutionListProvider<TFolder> : ExecutionListProvider
		where TFolder : class, IFolder
	{

		private readonly IFolderManager<TFolder> _folderManager;

		public ExecutionListProvider(IFolderManager<TFolder> folderManager)
		{
			_folderManager = folderManager;
		}

		public IEnumerable<TestFolder<TFolder>> Suite(TFolder folder)
		{
			var result = Enumerable.Empty<TestFolder<TFolder>>();
			foreach (var child in GetChildren(folder).Where(o => o.FolderType == FolderType.Suite))
			{
				result = result.Concat(Suite(child));
			}
			return result.Concat(ProvideExecutionList(folder, o => true));
		}

		private IEnumerable<TFolder> GetChildren(TFolder folder)
		{
			return _folderManager.GetChildren(folder);
		}

		private IEnumerable<TestFolder<TFolder>> ProvideExecutionList(TFolder containingFolder, Predicate<TFolder> filter)
		{
			if (GetChildren(containingFolder) == null)
				return Enumerable.Empty<TestFolder<TFolder>>();

			var suiteSetupFolder = FindFolderUpwards(containingFolder, SuiteSetUpFolderName);
			var suiteTearDownFolder = FindFolderUpwards(containingFolder, SuiteTearDownFolderName);
			var setupFolder = FindFolderUpwards(containingFolder, SetUpFolderName);
			var tearDownFolder = FindFolderUpwards(containingFolder, TearDownFolderName);

			var provideExecutionList = SingleOrEmpty(suiteSetupFolder)
				.Concat(GetChildren(containingFolder).Where(o => o.FolderType == FolderType.Test && filter(o)))
				.Concat(SingleOrEmpty(suiteTearDownFolder));

			return from f in provideExecutionList
			       select new TestFolder<TFolder>(f, setupFolder, tearDownFolder);
		}

		private TFolder FindFolderUpwards(TFolder containingFolder, string folderName)
		{
			while (containingFolder != null)
			{
				if (containingFolder.Name == folderName)
					return containingFolder;

				var suiteSetupFolder = GetChildren(containingFolder).SingleOrDefault(o => o.Name == folderName);
				if (suiteSetupFolder != null)
					return suiteSetupFolder;
				
				containingFolder = GetParent(containingFolder);
			}
			return null;
		}

		private TFolder GetParent(TFolder containingFolder)
		{
			return _folderManager.GetParent(containingFolder);
		}

		private static IEnumerable<T> SingleOrEmpty<T>(T element) 
			where T : class
		{
			return (element == null
			        	? Enumerable.Empty<T>()
			        	: new[] {element});
		}

		public IEnumerable<TestFolder<TFolder>> Test(TFolder folder)
		{
			return ProvideExecutionList(GetParent(folder), o => o == folder);
		}

		public TFolder GetRoot(TFolder folder)
		{
			return FindFolderUpwards(folder, RootFolderName);
		}
	}

	public class TestFolder<TFolder>
	{
		public TFolder Folder { get; private set; }
		public TFolder SetUpFolder { get; private set; }
		public TFolder TearDownFolder { get; private set; }

		public TestFolder(TFolder folder, TFolder setUpFolder, TFolder tearDownFolder)
		{
			Folder = folder;
			SetUpFolder = setUpFolder;
			TearDownFolder = tearDownFolder;
		}
	}
}